package com.gitee.l0km.codegen.webclient;

import java.lang.reflect.Type;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.namespace.QName;

import org.apache.axis2.description.AxisMessage;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.java2wsdl.TypeTable;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.ws.commons.schema.XmlSchema;

import com.gitee.l0km.codegen.base.AbstractSchema;
import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.wsdl.WebServiceInfo;
import com.gitee.l0km.com4j.base.Assert;
import com.gitee.l0km.com4j.basex.TypeNameUtils;

public class KSoapStub<T> extends AbstractSchema implements KSoapConstants {
	private final WebServiceInfo webServiceInfo;
	private final AxisService service;
	protected final Class<?> serviceClass;
	private final Map<QName, ComplexType> complexTypeBeans = new HashMap<QName, ComplexType>();
	private final Map<QName, ComplexType> complexTypeRequests = new HashMap<QName, ComplexType>();
	private final Map<QName, ComplexType> complexTypeResponses = new HashMap<QName, ComplexType>();
	private final Map<QName, ComplexType> complexTypeFaults = new HashMap<QName, ComplexType>();
	private final Map<QName, ComplexType> complexTypeOthers = new HashMap<QName, ComplexType>();
	private final Map<String, Map<QName, ComplexType>> schemaMap = new HashMap<String, Map<QName, ComplexType>>() {
		private static final long serialVersionUID = 1L;
		{
			put(TypePurpose.BEAN.name(), complexTypeBeans);
			put(TypePurpose.REQUEST.name(), complexTypeRequests);
			put(TypePurpose.RESPONSE.name(), complexTypeResponses);
			put(TypePurpose.FAULT.name(), complexTypeFaults);
			put(TypePurpose.OTHER.name(), complexTypeOthers);
		}
	};

	public KSoapStub(Class<?> serviceClass, URI wsdlURI, String serviceName) {
		Assert.notNull(wsdlURI,"wsdlURI");
		Assert.notNull(serviceClass, "serviceClass");
		this.serviceClass=serviceClass;
		webServiceInfo = new WebServiceInfo(wsdlURI, serviceClass, serviceName);
		service = this.webServiceInfo.getAxisService();
	}

	@Override
	public boolean compile() {
		boolean compileOk = false;
		try{
			addImportedClassFromMethods(webServiceInfo.getPorts().values(), true, true, true);
			this.createSchemaForBeans();
			this.createSchemaForPorts();
			this.createSchemaForOthers();
			compileOk = true;
		}finally{
			
		}
		return compileOk;
	}

	private void createSchemaForBeans() {
		Entry<QName, Class<?>> entry;
		for (Iterator<Entry<QName, Class<?>>> it = this.webServiceInfo.getQNameToClassMap().entrySet().iterator(); it
				.hasNext();) {
			entry = it.next();
			complexTypeBeans.put(entry.getKey(), new ComplexType(
					webServiceInfo.getXmlSchemaComplexType(entry.getKey()), webServiceInfo, null, null));
		}
	}

	private void createSchemaForOthers() {
		QName name;
		for (XmlSchema schema : this.webServiceInfo.getSchema()) {
			for (@SuppressWarnings("unchecked")
			Iterator<QName> it = schema.getSchemaTypes().getNames(); it.hasNext();) {
				name = it.next();
				if (!this.hasComplexType(name)) {
					complexTypeOthers.put(name, new ComplexType(webServiceInfo.getXmlSchemaComplexType(name),
							webServiceInfo, null, null));
				}
			}
		}
	}

	private final void setComplexTypeUsedRelatedBy(Class<?>... classes) {
		String className;
		QName name;
		TypeTable typeTable = this.service.getTypeTable();
		ComplexType complexType;

		for (Class<?> clazz : classes) {
			if (null != clazz) {
				clazz = CodeGenUtils.getElementClass(clazz);
				if (!typeTable.isSimpleType(className = clazz.getName())) {
					if (null != (name = typeTable.getQNamefortheType(className))) {
						if (null != (complexType = this.complexTypeBeans.get(name))) {
							complexType.setUsed(true);
						} else
							throw new IllegalArgumentException(String.format("can't found complex type for QName [%s]",
									name));
					} else
						throw new IllegalArgumentException(String.format("can't found QName for [%s] in typeTable",
								className));
				}
			}
		}
	}
	
	private final void setComplexTypeUsedRelatedBy(Type... types) {
		for (Type type : types) {
			if (null != type) {
				if (type instanceof Class) {
					setComplexTypeUsedRelatedBy((Class<?>) type);
				} else {
					setComplexTypeUsedRelatedBy(TypeNameUtils.getAllClassForGenericType(type.toString()));					
				}
			}
		}
	}

	private void createSchemaForPorts() {
		Entry<String, Method> entry;
		QName qn;
		AxisOperation op;
		ComplexType complexType;
		AxisMessage msg;
		boolean used=true;
		for (Iterator<Entry<String, Method>> it = this.webServiceInfo.getPorts().entrySet().iterator(); it.hasNext();) {
			entry = it.next();
			op = service.getOperation(new QName(service.getTargetNamespace(), entry.getKey()));
			try {
				msg = op.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
				qn = msg.getElementQName();
				complexType = new ComplexType(msg.getSchemaElement(), webServiceInfo, entry.getValue()
						.getGenericParameterTypes(), op);
				complexType.setUsed(used);
				setComplexTypeUsedRelatedBy(entry.getValue().getGenericParameterTypes());
				this.complexTypeRequests.put(qn, complexType);
			} catch (UnsupportedOperationException e) {

			}
			try {
				msg = op.getMessage(WSDLConstants.MESSAGE_LABEL_OUT_VALUE);
				qn = msg.getElementQName();
				complexType = new ComplexType(msg.getSchemaElement(),webServiceInfo, entry.getValue().getGenericReturnType(), op);
				complexType.setUsed(used);
				setComplexTypeUsedRelatedBy(entry.getValue().getGenericReturnType());
				this.complexTypeResponses.put(qn, complexType);
			} catch (UnsupportedOperationException e) {
			}
			setComplexTypeUsedRelatedBy(entry.getValue().getGenericExceptionTypes());
			for (AxisMessage fault : op.getFaultMessages()) {
				qn = fault.getElementQName();
				if (!complexTypeFaults.containsKey(qn)) {
					complexType = new ComplexType(fault.getSchemaElement(),webServiceInfo, FAULT_MESSAGE, op);
					complexType.setUsed(used);
					this.complexTypeFaults.put(qn, complexType);
				}
			}
		}
	}

	public ComplexType getComplexType(QName name) {
		ComplexType complexType = null;
		for (Map<QName, ComplexType> schema : schemaMap.values()) {
			if (null != (complexType = schema.get(name)))
				return complexType;
		}
		return complexType;
	}

	/**
	 * @return webServiceInfo
	 */
	public WebServiceInfo getWebServiceInfo() {
		return webServiceInfo;
	}

	public boolean hasComplexType(QName name) {
		return getComplexType(name) != null;
	}

	/**
	 * @return complexTypeBeans
	 */
	public Map<QName, ComplexType> getComplexTypeBeans() {
		return complexTypeBeans;
	}

	/**
	 * @return complexTypeRequests
	 */
	public Map<QName, ComplexType> getComplexTypeRequests() {
		return complexTypeRequests;
	}

	/**
	 * @return complexTypeResponses
	 */
	public Map<QName, ComplexType> getComplexTypeResponses() {
		return complexTypeResponses;
	}

	/**
	 * @return complexTypeFaults
	 */
	public Map<QName, ComplexType> getComplexTypeFaults() {
		return complexTypeFaults;
	}

	/**
	 * @return complexTypeOthers
	 */
	public Map<QName, ComplexType> getComplexTypeOthers() {
		return complexTypeOthers;
	}

	/**
	 * @return schemaMap
	 */
	public Map<String, Map<QName, ComplexType>> getSchemaMap() {
		return schemaMap;
	}

	/**
	 * @param name
	 * @see java.util.Map#get(java.lang.Object)
	 */
	public Map<QName, ComplexType> getSchema(String name) {
		return schemaMap.get(name);
	}

	/**
	 * @return serviceClass
	 */
	public Class<?> getServiceClass() {
		return serviceClass;
	}
	
	
}
